• 问题

    类的内部成员域为引用类型数据,而非是基本类型数据时,就要在类构造器和成员域的访问方法上考虑对这些内部成员域进行保护,以防止外界条件破坏了这种约束,对实例对象的状态发生改变。那么,对类的成员域为引用类型数据时,应该怎样处理?

  • 解决

    1. 对类构造器进行保护性拷贝

      示例代码为:

      import java.util.Date;
      
      public final class Period {
          private final Date start;
          private final Date end;
          public Period(Date start,Date end) {
              if(start.compareTo(end) > 0){
                  throw new IllegalArgumentException(start + " after " + end);
              }
              this.start = start;
              this.end = end;
          }
      
          public Date start(){
              return start;
          }
      
          public Date end(){
              return end;
          }
          //remainder omitted
      }
      

      这段代码看上去没有什么问题,本意以为是Period被构造后,状态是不会被改变的,但是由于Date是可变的,Period的状态也是会被改变的,如下面的这样的使用:

      Date start = new Date();
      Date end = new Date();
      Period period = new Period(start, end);
      end.setYear(78);
      

      因此,为了让Period更加安全可靠,需要对构造器进行保护性拷贝,将上面这段代码改变如下这种形式:

      public Period(Date start,Date end) {
          this.start = new Date(start.getTime());
          this.end = new Date(end.getTime());
          if(this.start.compareTo(this.end) > 0){
          throw new IllegalArgumentException(this.start + " after " + this.end);
          }
      }
      

      注意:保护性拷贝在参数有效性检查之前,并且参数有效性检查针对的是已拷贝的对象,而非是原始对象。

    2. 对类成员域进行保护性拷贝

      如果上例中的成员域提供了访问方法,那么,Period仍然是不安全的。如果不进行保护性拷贝的话,引用类型数据就有可能在类的外部被改变,因此影响类内部的数据结构,污染到类。针对成员域的访问方法,可做如下的保护性拷贝:

      public Date start() {
          return new Date(start.getTime());
      }
      
      public Date end() {
          return new Date(end.getTime());
      }
      
    3. 什么时候考虑使用保护性拷贝?

      每当编写方法或者构造器时,如果它允许由客户端提供对象进入到类的内部数据结构时,就需要考虑,客户端提供的对象是否有可能是可变的。如果是,就要考虑能否忍受对象可变时,对类的内部的数据结构发生改变。如果不能,则要在构造器或者方法上对外部对象进行保护性拷贝,让拷贝后的对象进入到类,而不是原始的可变的对象。例如,如果使用外部的对象作为Set的元素或者作为Map的key,就应该意识到,这个对象在插入之后再被修改,相应的Set或者Map就会遭到破坏。

  • 结论

    如果类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性地拷贝这些组件。如果拷贝的成本受到限制,并且类信任它的客户端不会不恰当地修改这些组件,就可以在文档中进行说明。

results matching ""

    No results matching ""